package com.black.utils.encrypt.sm4;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

/**
 * SM4加密算法的实现。
 * 该类支持ECB和CBC两种模式的加密和解密操作。
 */
public class SM4 {
    public static final int SM4_ENCRYPT = 1;

    public static final int SM4_DECRYPT = 0;

    /**
     * 从字节数组中以大端字节序获取一个长整型数值。
     * 此方法用于解析字节数组中特定位置开始的4个字节，作为大端字节序的长整型值。
     * 大端字节序意味着字节数组中的第一个字节是数值的最高位。
     *
     * @param b 字节数组，包含要解析的长整型数值。
     * @param i 开始解析的字节索引。注意，此索引是相对于整个字节数组的。
     * @return 从字节数组中解析出的长整型数值，以大端字节序表示。
     */
    private long GET_LONG_BE(byte[] b, int i) {
        return (long) (b[i] & 0xff) << 24 | (long) ((b[i + 1] & 0xff) << 16) | (long) ((b[i + 2] & 0xff) << 8) | (long) (b[i + 3] & 0xff) & 0xffffffffL;
    }

    /**
     * 将一个长整型数字以大端字节序的形式存储到字节数组中。
     * 此方法用于将长整型数字转换为字节序列，以大端字节序（最高有效字节在最前面）的形式存储在字节数组中，
     * 适用于需要将长整型数据进行网络传输或存储到字节流中等场景。
     *
     * @param n 需要转换的长整型数字。
     * @param b 目标字节数组，用于存储转换后的字节序列。
     * @param i 在字节数组中开始存储的索引位置。
     */
    private void PUT_LONG_BE(long n, byte[] b, int i) {
        // 从n的高4位开始，依次取出每个字节，并存储到字节数组b中
        b[i] = (byte) (int) (0xFF & n >> 24);
        b[i + 1] = (byte) (int) (0xFF & n >> 16);
        b[i + 2] = (byte) (int) (0xFF & n >> 8);
        b[i + 3] = (byte) (int) (0xFF & n);
    }

    /**
     * 实现了一个长整型数值的按位左移操作。
     * 此函数的目的是在不改变数值高位的情况下，将给定的长整型数值x左移n位。
     * 使用按位与操作符和一个特定的掩码0xFFFFFFFFL，以确保只操作数值的低位，
     * 这样可以避免因左移操作导致的高位丢失问题。
     *
     * @param x 待进行左移操作的长整型数值。
     * @param n 左移的位数，可以是任意整数。
     * @return 返回左移操作后的结果。
     */
    private long SHL(long x, int n) {
        return (x & 0xFFFFFFFFL) << n;
    }

    /**
     * 实现了一个循环左移函数。
     * 循环左移是一种常见的位操作，用于加密、数据压缩和哈希函数等领域。
     * 该函数将一个长整型数值x循环左移n位，并以长整型返回结果。
     * 循环左移操作是通过将x左移n位并与原始x的右移(32-n)位结果进行或操作来实现的。
     * 这样做可以确保移动后的高位被正确地填充到低位，从而实现循环移位的效果。
     *
     * @param x 待操作的长整型数值。
     * @param n 左移的位数，是一个整型值。
     * @return 返回循环左移n位后的结果。
     */
    private long ROT_L(long x, int n) {
        // 将x左移n位，并与原始x的右移(32-n)位结果进行或操作，实现循环左移的效果。
        return SHL(x, n) | x >> (32 - n);
    }

    /**
     * 对数组中的两个元素进行交换。
     * 该方法用于特定条件下的数组元素交换，交换位置由索引i和31-i决定。
     * 这种交换操作可能用于某些算法中，比如排序或构建特定的数据结构。
     *
     * @param sk 需要进行元素交换的数组。
     * @param i  用于确定要交换的两个元素位置的索引。
     */
    private void SWAP(long[] sk, int i) {
        // 临时存储索引i处的元素
        long t = sk[i];
        // 将索引i处的元素替换为索引31-i处的元素
        sk[i] = sk[(31 - i)];
        // 将索引31-i处的元素替换为之前存储的索引i处的元素
        sk[(31 - i)] = t;
    }


    public static final byte[] SboxTable = {(byte) 0xd6, (byte) 0x90, (byte) 0xe9, (byte) 0xfe,
            (byte) 0xcc, (byte) 0xe1, 0x3d, (byte) 0xb7, 0x16, (byte) 0xb6,
            0x14, (byte) 0xc2, 0x28, (byte) 0xfb, 0x2c, 0x05, 0x2b, 0x67,
            (byte) 0x9a, 0x76, 0x2a, (byte) 0xbe, 0x04, (byte) 0xc3,
            (byte) 0xaa, 0x44, 0x13, 0x26, 0x49, (byte) 0x86, 0x06,
            (byte) 0x99, (byte) 0x9c, 0x42, 0x50, (byte) 0xf4, (byte) 0x91,
            (byte) 0xef, (byte) 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43,
            (byte) 0xed, (byte) 0xcf, (byte) 0xac, 0x62, (byte) 0xe4,
            (byte) 0xb3, 0x1c, (byte) 0xa9, (byte) 0xc9, 0x08, (byte) 0xe8,
            (byte) 0x95, (byte) 0x80, (byte) 0xdf, (byte) 0x94, (byte) 0xfa,
            0x75, (byte) 0x8f, 0x3f, (byte) 0xa6, 0x47, 0x07, (byte) 0xa7,
            (byte) 0xfc, (byte) 0xf3, 0x73, 0x17, (byte) 0xba, (byte) 0x83,
            0x59, 0x3c, 0x19, (byte) 0xe6, (byte) 0x85, 0x4f, (byte) 0xa8,
            0x68, 0x6b, (byte) 0x81, (byte) 0xb2, 0x71, 0x64, (byte) 0xda,
            (byte) 0x8b, (byte) 0xf8, (byte) 0xeb, 0x0f, 0x4b, 0x70, 0x56,
            (byte) 0x9d, 0x35, 0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, (byte) 0xd1,
            (byte) 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, (byte) 0x87,
            (byte) 0xd4, 0x00, 0x46, 0x57, (byte) 0x9f, (byte) 0xd3, 0x27,
            0x52, 0x4c, 0x36, 0x02, (byte) 0xe7, (byte) 0xa0, (byte) 0xc4,
            (byte) 0xc8, (byte) 0x9e, (byte) 0xea, (byte) 0xbf, (byte) 0x8a,
            (byte) 0xd2, 0x40, (byte) 0xc7, 0x38, (byte) 0xb5, (byte) 0xa3,
            (byte) 0xf7, (byte) 0xf2, (byte) 0xce, (byte) 0xf9, 0x61, 0x15,
            (byte) 0xa1, (byte) 0xe0, (byte) 0xae, 0x5d, (byte) 0xa4,
            (byte) 0x9b, 0x34, 0x1a, 0x55, (byte) 0xad, (byte) 0x93, 0x32,
            0x30, (byte) 0xf5, (byte) 0x8c, (byte) 0xb1, (byte) 0xe3, 0x1d,
            (byte) 0xf6, (byte) 0xe2, 0x2e, (byte) 0x82, 0x66, (byte) 0xca,
            0x60, (byte) 0xc0, 0x29, 0x23, (byte) 0xab, 0x0d, 0x53, 0x4e, 0x6f,
            (byte) 0xd5, (byte) 0xdb, 0x37, 0x45, (byte) 0xde, (byte) 0xfd,
            (byte) 0x8e, 0x2f, 0x03, (byte) 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b,
            0x51, (byte) 0x8d, 0x1b, (byte) 0xaf, (byte) 0x92, (byte) 0xbb,
            (byte) 0xdd, (byte) 0xbc, 0x7f, 0x11, (byte) 0xd9, 0x5c, 0x41,
            0x1f, 0x10, 0x5a, (byte) 0xd8, 0x0a, (byte) 0xc1, 0x31,
            (byte) 0x88, (byte) 0xa5, (byte) 0xcd, 0x7b, (byte) 0xbd, 0x2d,
            0x74, (byte) 0xd0, 0x12, (byte) 0xb8, (byte) 0xe5, (byte) 0xb4,
            (byte) 0xb0, (byte) 0x89, 0x69, (byte) 0x97, 0x4a, 0x0c,
            (byte) 0x96, 0x77, 0x7e, 0x65, (byte) 0xb9, (byte) 0xf1, 0x09,
            (byte) 0xc5, 0x6e, (byte) 0xc6, (byte) 0x84, 0x18, (byte) 0xf0,
            0x7d, (byte) 0xec, 0x3a, (byte) 0xdc, 0x4d, 0x20, 0x79,
            (byte) 0xee, 0x5f, 0x3e, (byte) 0xd7, (byte) 0xcb, 0x39, 0x48};

    public static final int[] FK = {0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc};

    public static final int[] CK = {0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
            0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
            0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
            0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
            0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
            0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
            0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
            0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279};

    /**
     * 对输入的字节进行SM4标准的S盒变换。
     * <p>
     * SM4的S盒变换是加密和解密过程中关键的非线性变换步骤，它通过查表方式将输入的8位字节
     * 映射到另一个8位字节，以增加加密的复杂度和安全性。
     *
     * @param inch 输入的字节，经过与0xFF按位与操作后作为查表的索引。
     * @return 返回S盒查表得到的结果字节。
     */
    private byte SM4SBox(byte inch) {
        // 将输入字节的8位二进制值作为索引，用于查表。
        int i = inch & 0xFF;
        // 根据索引查表SboxTable，并返回查表结果。
        return SboxTable[i];
    }


    /**
     * SM4算法的LT变换函数。
     * 这个函数用于对输入的64位长整型数值进行特定的位运算变换，是SM4加密算法中的一个重要组成部分。
     * LT变换通过一系列的位旋转和异或操作，改变了输入值的位模式，为加密过程增加了复杂性。
     *
     * @param ka 输入的64位长整型数值。
     * @return 经过LT变换后的64位长整型数值。
     */
    private long SM4LT(long ka) {
        // 通过getBb函数获取输入数值ka的特定位模式
        long bb = getBb(ka);
        // 对bb进行4次位旋转，并分别与原bb值进行异或操作，最后将4个结果再次异或得到最终的变换结果
        return bb ^ ROT_L(bb, 2) ^ ROT_L(bb, 10) ^ ROT_L(bb, 18) ^ ROT_L(bb, 24);
    }


    /**
     * SM4算法的F函数。
     * <p>
     * F函数是SM4加密算法中的核心函数，用于计算每个轮的子密钥与明文块的异或结果。
     * 这里接收四个长整型参数和一个轮密钥 rk，通过对这些参数进行异或操作和调用SM4LT函数，
     * 最终返回异或操作的结果。
     * <p>
     *
     * @param x0 第一个输入参数，代表明文块的第0个字节。
     * @param x1 第二个输入参数，代表明文块的第1个字节。
     * @param x2 第三个输入参数，代表明文块的第2个字节。
     * @param x3 第四个输入参数，代表明文块的第3个字节。
     * @param rk 轮密钥，用于与明文块的字节进行异或操作。
     * @return 返回x0与SM4LT函数返回值异或的结果。
     */
    private long SM4F(long x0, long x1, long x2, long x3, long rk) {
        return x0 ^ SM4LT(x1 ^ x2 ^ x3 ^ rk);
    }


    /**
     * 计算SM4算法的RK值。
     * <p>
     * 本函数用于SM4加密算法中计算轮密钥（Round Key）的一部分过程。在SM4算法中，轮密钥的生成
     * 需要对初始密钥进行多次变换操作，包括S盒变换、异或操作和循环左移等。此函数具体实现了
     * 对中间结果进行异或和循环左移的操作，以生成最终的轮密钥。
     *
     * @param ka 初始密钥的中间变换结果，用于计算轮密钥。
     * @return 返回计算得到的轮密钥的一部分。
     */
    private long SM4RK(long ka) {
        // 通过getBb方法获取中间结果bb
        long bb = getBb(ka);
        // 对bb进行循环左移操作，并进行异或操作，得到最终的轮密钥部分
        // 这里的循环左移长度（13和23）是SM4算法规定的固定值
        return bb ^ ROT_L(bb, 13) ^ ROT_L(bb, 23);
    }


    /**
     * 对给定的长整型数值进行SM4算法的字节转换和S盒操作。
     * 此函数主要用于SM4加密算法的内部处理，将一个长整型数值转换为字节序列，
     * 并对每个字节应用SM4的S盒变换。这是SM4加密算法的关键步骤之一，
     * 用于增加加密的非线性和混淆效果。
     *
     * @param ka 需要进行S盒变换的长整型数值。
     * @return 经过S盒变换后的长整型数值。
     */
    private long getBb(long ka) {
        // 初始化两个字节数组，用于存放原始字节和经过S盒变换的字节
        byte[] a = new byte[4];
        byte[] b = new byte[4];

        // 将长整型数值ka转换为大端字节序列，并存放在数组a中
        PUT_LONG_BE(ka, a, 0);

        // 对数组a中的每个字节应用SM4的S盒变换
        b[0] = SM4SBox(a[0]);
        b[1] = SM4SBox(a[1]);
        b[2] = SM4SBox(a[2]);
        b[3] = SM4SBox(a[3]);

        // 将经过S盒变换后的字节序列转换回长整型数值，并以大端方式返回
        return GET_LONG_BE(b, 0);
    }


    /**
     * 设置SM4加密算法的密钥。
     * 该方法用于初始化SM4加密算法的扩展密钥（SK）。它首先从输入的字节型密钥（key）中提取出明文密钥（MK），
     * 然后通过明文密钥和固定的常量数组FK以及轮常量数组CK，生成加密所需的32个扩展密钥。
     *
     * @param ctx 密钥上下文对象，用于存储密钥和扩展密钥。
     * @throws Exception 如果密钥数组为空或长度不为16，则抛出异常。
     */
    private void SM4SetKey(SM4Context ctx) throws Exception {
        // 扩展密钥数组，用于存储生成的32个扩展密钥。
        long[] SK = ctx.sk;
        // 原始的字节型密钥，长度为16字节。
        byte[] key = ctx.key;

        // 检查密钥数组是否为空或长度不为16，如果不符合要求，则抛出异常。
        if (key == null || key.length != 16) {
            throw new Exception("key error!");
        }
        // 初始化明文密钥数组
        long[] MK = new long[4];
        // 初始化用于生成扩展密钥的临时数组
        long[] k = new long[36];
        int i = 0;

        // 从字节型密钥中按大端字节序提取出明文密钥的4个long型值
        MK[0] = GET_LONG_BE(key, 0);
        MK[1] = GET_LONG_BE(key, 4);
        MK[2] = GET_LONG_BE(key, 8);
        MK[3] = GET_LONG_BE(key, 12);

        // 将明文密钥与常量数组FK的值进行异或操作，初始化临时数组k的前4个值
        k[0] = MK[0] ^ (long) FK[0];
        k[1] = MK[1] ^ (long) FK[1];
        k[2] = MK[2] ^ (long) FK[2];
        k[3] = MK[3] ^ (long) FK[3];

        // 循环生成剩余的扩展密钥
        for (; i < 32; i++) {
            // 根据当前轮的计算公式生成下一个扩展密钥，并将其存入SK数组
            k[(i + 4)] = (k[i] ^ SM4RK(k[(i + 1)] ^ k[(i + 2)] ^ k[(i + 3)] ^ (long) CK[i]));
            SK[i] = k[(i + 4)];
        }
    }

    /**
     * 执行SM4算法的一个轮次加密或解密操作。
     *
     * @param sk     密钥数组，包含32个字节的密钥材料。
     * @param input  输入的明文或密文，必须是4个字节的倍数。
     * @param output 输出的密文或明文，长度与输入相同。
     *               <p>
     *               注意：该方法假设输入和输出数组的长度至少为16个字节，并且sk数组包含正确的密钥材料。
     */
    private void SM4OneRound(long[] sk, byte[] input, byte[] output) {
        /* 初始化索引变量，并声明一个长整型数组用于临时存储字节数据和中间计算结果。 */
        int i = 0;
        long[] longs = new long[36];

        /* 从输入字节数组中按大端字节序提取前4个长整型值，并存储到longs数组的前4个位置。 */
        longs[0] = GET_LONG_BE(input, 0);
        longs[1] = GET_LONG_BE(input, 4);
        longs[2] = GET_LONG_BE(input, 8);
        longs[3] = GET_LONG_BE(input, 12);

        /* 对longs数组中的值进行32次迭代计算，每次计算都使用当前轮的密钥材料。 */
        while (i < 32) {
            /* 在每次迭代中，使用SM4F函数计算新的值，并更新longs数组。 */
            /* i + 4的位置存储的是基于当前i位置及周围三个值和密钥计算出的新值。 */
            longs[(i + 4)] = SM4F(longs[i], longs[(i + 1)], longs[(i + 2)], longs[(i + 3)], sk[i]);
            i++;
        }

        /* 将计算后的longs数组的最后4个长整型值按大端字节序写入输出字节数组。 */
        PUT_LONG_BE(longs[35], output, 0);
        PUT_LONG_BE(longs[34], output, 4);
        PUT_LONG_BE(longs[33], output, 8);
        PUT_LONG_BE(longs[32], output, 12);
    }

    /**
     * 对输入的字节数组进行填充或解填充。
     * 根据模式的不同，执行不同的填充方式。用于SM4加密算法。
     * 加密时，使用PKCS7填充方式；解密时，去除填充。
     *
     * @param input 待处理的字节数组。
     * @param mode  模式，SM4_ENCRYPT表示加密，其他表示解密。
     * @return 填充或解填充后的字节数组。
     */
    private byte[] padding(byte[] input, int mode) {
        // 如果输入为null，则直接返回null。
        if (input == null) {
            return null;
        }

        byte[] ret = null;
        // 根据模式执行不同的填充逻辑。
        if (mode == SM4_ENCRYPT) {
            // 计算需要填充的字节数。
            int p = 16 - input.length % 16;
            // 初始化结果数组，长度为原数组长度加上填充的字节数。
            ret = new byte[input.length + p];
            // 将原数组内容复制到结果数组。
            System.arraycopy(input, 0, ret, 0, input.length);
            // 对结果数组进行PKCS7填充。
            for (int i = 0; i < p; i++) {
                ret[input.length + i] = (byte) p;
            }
        } else {
            // 解密时，获取最后一个字节的值，该值表示填充的字节数。
            int p = input[input.length - 1];
            // 初始化结果数组，长度为原数组长度减去填充的字节数。
            ret = new byte[input.length - p];
            // 将非填充字节复制到结果数组。
            System.arraycopy(input, 0, ret, 0, input.length - p);
        }
        return ret;
    }


    /**
     * 设置SM4加密模式的密钥。
     * <p>
     * 本函数用于在给定的SM4上下文中设置用于加密的密钥。调用此函数后，上下文将处于加密模式。
     *
     * @param ctx SM4上下文对象，用于存储加密/解密相关的状态信息。
     * @throws Exception 如果上下文对象为空或密钥无效（长度不为16字节），则抛出异常。
     */
    public void SM4SetKeyEnc(SM4Context ctx) throws Exception {
        // 检查上下文对象是否为空，如果为空，则抛出异常。
        if (ctx == null) {
            throw new Exception("ctx is null!");
        }

        // 将上下文的模式设置为加密模式。
        ctx.mode = SM4_ENCRYPT;
        // 调用SM4SetKey函数，初始化上下文的密钥。
        SM4SetKey(ctx);
    }


    /**
     * 设置SM4解密密钥。
     * <p>
     * 本函数用于在给定的SM4上下文中设置解密密钥。它首先验证上下文和密钥的有效性，
     * 然后初始化上下文以用于解密操作，并对密钥进行预处理。
     *
     * @param ctx SM4加密上下文，不能为null。
     * @throws Exception 如果上下文或密钥无效，则抛出异常。
     */
    public void SM4SetKeyDec(SM4Context ctx) throws Exception {
        // 验证上下文对象是否为null
        if (ctx == null) {
            throw new Exception("ctx is null!");
        }
        // 初始化解密模式
        ctx.mode = SM4_DECRYPT;
        // 设置密钥，此函数可能负责密钥的扩展或其他预处理
        SM4SetKey(ctx);
        // 对密钥数组进行字节交换，以适应解密过程
        for (int i = 0; i < 16; i++) {
            SWAP(ctx.sk, i);
        }
    }


    /**
     * 使用SM4算法进行ECB模式的加密或解密。
     *
     * @param ctx   SM4上下文，包含密钥和其他配置信息。
     * @param input 待加密或解密的数据。
     * @return 加密或解密后的数据。
     * @throws Exception 如果输入数据为空或在加密/解密过程中发生错误。
     */
    public byte[] SM4CryptEcb(SM4Context ctx, byte[] input) throws Exception {
        // 检查输入数据是否为空
        if (input == null) {
            throw new Exception("input is null!");
        }

        // 如果是加密操作且使用了填充方式，则对输入数据进行填充
        if ((ctx.isPadding) && (ctx.mode == SM4_ENCRYPT)) {
            input = padding(input, SM4_ENCRYPT);
        }

        // 初始化输入和输出流
        int length = input.length;
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(input);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        // 对数据分块进行加密或解密
        for (; length > 0; length -= 16) {
            byte[] in = new byte[16];
            byte[] out = new byte[16];
            byteArrayInputStream.read(in);
            SM4OneRound(ctx.sk, in, out);
            byteArrayOutputStream.write(out);
        }

        // 获取加密或解密后的完整数据
        byte[] output = byteArrayOutputStream.toByteArray();

        // 如果是解密操作且使用了填充方式，则对输出数据进行填充的去除
        if (ctx.isPadding && ctx.mode == SM4_DECRYPT) {
            output = padding(output, SM4_DECRYPT);
        }

        // 关闭输入输出流
        byteArrayInputStream.close();
        byteArrayOutputStream.close();

        // 返回加密或解密后的数据
        return output;
    }


    /**
     * SM4加密/解密函数，使用CBC模式。
     *
     * @param ctx   SM4上下文，包含密钥和其他加密/解密相关信息。
     * @param input 待加密/解密的数据。
     * @return 加密/解密后的字节数组。
     * @throws Exception 如果初始化向量长度不正确或输入数据为空，则抛出异常。
     */
    public byte[] SM4CryptCbc(SM4Context ctx, byte[] input) throws Exception {
        byte[] iv = ctx.iv;
        // 检查初始化向量是否为空或长度不为16
        if (iv == null || iv.length != 16) {
            throw new Exception("iv error!");
        }

        // 检查输入数据是否为空
        if (input == null) {
            throw new Exception("input is null!");
        }

        // 如果是加密模式且需要填充，则对输入数据进行填充
        if (ctx.isPadding && ctx.mode == SM4_ENCRYPT) {
            input = padding(input, SM4_ENCRYPT);
        }

        int i = 0;
        int length = input.length;
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(input);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        // 根据加密或解密模式执行操作
        if (ctx.mode == SM4_ENCRYPT) {
            // 加密模式下的循环处理
            for (; length > 0; length -= 16) {
                byte[] in = new byte[16];
                byte[] out = new byte[16];
                byte[] out1 = new byte[16];

                byteArrayInputStream.read(in);
                // 对每个块的明文与上一个加密块的结果进行异或
                for (i = 0; i < 16; i++) {
                    out[i] = ((byte) (in[i] ^ iv[i]));
                }
                // 执行SM4单轮加密
                SM4OneRound(ctx.sk, out, out1);
                // 更新初始化向量
                System.arraycopy(out1, 0, iv, 0, 16);
                // 将加密后的块写入输出流
                byteArrayOutputStream.write(out1);
            }
        } else {
            // 解密模式下的循环处理
            byte[] temp = new byte[16];
            for (; length > 0; length -= 16) {
                byte[] in = new byte[16];
                byte[] out = new byte[16];
                byte[] out1 = new byte[16];

                byteArrayInputStream.read(in);
                // 保存当前块以用于更新初始化向量
                System.arraycopy(in, 0, temp, 0, 16);
                // 执行SM4单轮解密
                SM4OneRound(ctx.sk, in, out);
                // 对解密后的块与初始化向量进行异或
                for (i = 0; i < 16; i++) {
                    out1[i] = ((byte) (out[i] ^ iv[i]));
                }
                // 更新初始化向量
                System.arraycopy(temp, 0, iv, 0, 16);
                // 将解密后的块写入输出流
                byteArrayOutputStream.write(out1);
            }
        }

        // 获取最终的加密/解密结果
        byte[] output = byteArrayOutputStream.toByteArray();
        // 如果是解密模式且需要填充，则对结果进行填充处理
        if (ctx.isPadding && ctx.mode == SM4_DECRYPT) {
            output = padding(output, SM4_DECRYPT);
        }
        // 关闭输入输出流
        byteArrayInputStream.close();
        byteArrayOutputStream.close();
        // 返回加密/解密后的数据
        return output;
    }
}



